package com.alkalinesolutions.json.core.impl;

import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.lang.reflect.UndeclaredThrowableException;

import com.alkalinesolutions.json.core.JSONException;

public class CompactJSONTextEmitter {
	protected Writer writer;
	protected boolean first;

	public CompactJSONTextEmitter() {
		writer = null;
		first = true;
	}

	protected void reset() {
		first = true;
	}
	
	public Writer getWriter() {
		return writer;
	}

	public void setWriter(final Writer writer) {
		this.writer = writer;
	}
	
	public void emitJSONText(Iterable<JSONEvent> events) throws JSONException {
		reset();
		try {
			for (JSONEvent ev: events) {
				event(ev);
			}
		}
		catch (UndeclaredThrowableException e) {
			if (e.getCause() instanceof JSONException) {
				throw  (JSONException)e.getCause();
			}
			throw new JSONException(e.getCause());
		}
	}

	protected void outputString(final String str) throws IOException {
		writer.write('\"');
		final int strlength = str.length();
		for (int i = 0; i < strlength; i++) {
			final char ch = str.charAt(i);
			switch (ch) {
			case '\n':
				writer.write("\\n"); //$NON-NLS-1$
				break;
			case '\t':
				writer.write("\\t"); //$NON-NLS-1$
				break;
			case '\f':
				writer.write("\\f"); //$NON-NLS-1$
				break;
			case '\r':
				writer.write("\\r"); //$NON-NLS-1$
				break;
			case '\b':
				writer.write("\\b"); //$NON-NLS-1$
				break;
			case '\\':
				writer.write("\\\\"); //$NON-NLS-1$
				break;
			case '\"':
				writer.write("\\\""); //$NON-NLS-1$
				break;
			default:
				if (ch < 32)
					writer.write(String.format("\\u%04x", new Integer(ch))); //$NON-NLS-1$
				else
					writer.write(ch);
			}
		}
		writer.write('\"');
	}

	protected void emitNext() throws IOException {
		if (!first)
			writer.write(',');

		first = false;
	}

	// JSONHandler implementation
	protected void event(JSONEvent event)  throws JSONException {
		String key = event.getKey();
		Serializable data = event.getData();
		
		try {
			switch (event.getType()) {
			case Boolean:
			case Number:
				emitNext();
				emitKey(key);
				writer.write(data.toString()); 
				break;
			case Null:
				emitNext();
				emitKey(key);
				writer.write("null"); //$NON-NLS-1$
				break;
			case String:
				emitNext();
				emitKey(key);
				outputString((String)data);
				break;
			case StartArray:
				startStruct(key, false);
				break;
			case EndArray:
				endStruct(false);
				break;
			case StartObject:
				startStruct(key, true);
				break;
			case EndObject:
				endStruct(true);
				break;
			}
		}
		catch (IOException e) {
			throw new JSONException(e);
		}
	}
	
	public void startStruct(String key, boolean object) throws IOException {
		emitNext();
		emitKey(key);
		writer.write(object ? '{' : '[');
		first = true;
	}

	public void endStruct(boolean object) throws IOException {
		writer.write(object ? '}' : ']');
		first = false;
	}

	protected void emitKey(final String k) throws IOException {
		if (k != null) {
			outputString(k);
			writer.write(':');
		}
	}
}